package com.code.user.config.token;

import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import com.code.common.exception.ServiceException;
import com.code.common.model.ResponseCode;
import com.code.user.constant.CacheKeyPrefixConst;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by IntelliJ IDEA.
 *
 * @author: Yan
 * @date: 2023/4/14
 *
 * 图形验证码登录
 */
public class CaptchaTokenGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "captcha";
    private final AuthenticationManager authenticationManager;
    private StringRedisTemplate stringRedisTemplate;

    public CaptchaTokenGranter(AuthenticationManager authenticationManager,
                               StringRedisTemplate stringRedisTemplate,
                               AuthorizationServerTokenServices tokenServices,
                               ClientDetailsService clientDetailsService,
                               OAuth2RequestFactory requestFactory){
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.stringRedisTemplate = stringRedisTemplate;
    }

    protected CaptchaTokenGranter(AuthenticationManager authenticationManager,
                                  AuthorizationServerTokenServices tokenServices,
                                  ClientDetailsService clientDetailsService,
                                  OAuth2RequestFactory requestFactory,
                                  String grantType) {
        //调用父类 接管GRANT_TYPE类型
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
        String username = parameters.get("username");
        String password = parameters.get("password");
        String captchaCode = parameters.get("captchaCode");

        this.validatorMobile(username);
        this.checkCaptchaCode(captchaCode);

        parameters.remove("password");
        Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
        ((AbstractAuthenticationToken)userAuth).setDetails(parameters);

        try {
            userAuth = this.authenticationManager.authenticate(userAuth);
        } catch (AccountStatusException | BadCredentialsException e) {
            throw new ServiceException(ResponseCode.USERNAME_PASSWORD_ERROR);
        }

        if (userAuth == null && !userAuth.isAuthenticated()) {
            throw new ServiceException(ResponseCode.USER_LOGIN_FAILURE);
        }

        OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }

    private void checkCaptchaCode(String code) {
        if (StrUtil.isBlank(code)){
            throw new ServiceException(ResponseCode.VERIFY_CODE_NOT_EMPTY);
        }

        // 获取 header 头部的 UUID
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String uuid = CacheKeyPrefixConst.CAPTCHA_CODE_PREFIX + request.getHeader("uuid");
        String cacheCodeValue = stringRedisTemplate.opsForValue().get(uuid);

        if (StrUtil.isBlank(cacheCodeValue)) {
            throw new ServiceException(ResponseCode.VERIFY_CODE_EXPIRED);
        }

        if (!StrUtil.equalsIgnoreCase(code, cacheCodeValue)) {
            throw new ServiceException(ResponseCode.VERIFY_CODE_ERROR);
        }

        stringRedisTemplate.delete(uuid);
    }

    private void validatorMobile(String username) {
        if (StrUtil.isBlank(username) && !Validator.isMobile(username)) {
            throw new ServiceException(ResponseCode.MOBILE_NOT_LEGAL);
        }
    }

}
